/**
 * 
 */
package graph;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.BoundedRangeModel;
import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.DefaultBoundedRangeModel;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import model.Data;

import org.apache.commons.collections15.Factory;
import org.apache.commons.collections15.Transformer;

import route.Route;
import route.Routes;
import spatial.MemoryRTree;
import spatial.NearestNeighborQuery;

import com.vividsolutions.jts.geom.Coordinate;

import edu.uci.ics.jung.algorithms.layout.CircleLayout;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.SparseMultigraph;
import edu.uci.ics.jung.io.PajekNetReader;
import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ScalingControl;
import edu.uci.ics.jung.visualization.decorators.AbstractEdgeShapeTransformer;
import edu.uci.ics.jung.visualization.decorators.ConstantDirectionalEdgeValueTransformer;
import edu.uci.ics.jung.visualization.decorators.EdgeShape;
import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.renderers.EdgeLabelRenderer;
import edu.uci.ics.jung.visualization.renderers.VertexLabelRenderer;
import facility.Facility;

/**
 * @author xiaohui
 * 
 */
public class Network extends JApplet {

	private static final long serialVersionUID = -6077157664507049647L;
	VisualizationViewer<Integer, Number> vv;
	MemoryRTree tree;

	// two renderers
	VertexLabelRenderer vertexLabelRenderer;
	EdgeLabelRenderer edgeLabelRenderer;

	ScalingControl scaler = new CrossoverScalingControl();

	Graph graph;
	Data data;

	// public static void main(String[] args) throws IOException {
	// JFrame jf = new JFrame();
	// Graph g = getGraph();
	// VisualizationViewer vv = new VisualizationViewer(new FRLayout(g));
	// jf.getContentPane().add(vv);
	// jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	// jf.pack();
	// jf.setVisible(true);
	// }

	public Network() throws IOException {
		graph = getGraph();
		data = new Data();
		tree = new MemoryRTree();
		/**
		 * insert the tree network nodes and edges
		 */

		Layout<Integer, Number> layout = new CircleLayout<Integer, Number>(
				graph);
		vv = new VisualizationViewer<Integer, Number>(layout, new Dimension(
				600, 400));
		vv.setBackground(Color.white);
		vertexLabelRenderer = vv.getRenderContext().getVertexLabelRenderer();
		edgeLabelRenderer = vv.getRenderContext().getEdgeLabelRenderer();

		// set transformer
		Transformer<Number, String> stringer = new Transformer<Number, String>() {
			public String transform(Number e) {
				return "Edge:" + graph.getEndpoints(e).toString();
			}
		};
		vv.getRenderContext().setEdgeLabelTransformer(stringer);

		// decorations
		vv.getRenderContext().setEdgeDrawPaintTransformer(
				new PickableEdgePaintTransformer<Number>(vv
						.getPickedEdgeState(), Color.black, Color.cyan));
		vv.getRenderContext().setVertexFillPaintTransformer(
				new PickableVertexPaintTransformer<Integer>(vv
						.getPickedVertexState(), Color.red, Color.yellow));

		// add my listener for ToolTips
		vv.setVertexToolTipTransformer(new ToStringLabeller<Integer>());

		// create a frome to hold the graph
		final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
		Container content = getContentPane();
		content.add(panel);

		final DefaultModalGraphMouse<Integer, Number> graphMouse = new DefaultModalGraphMouse<Integer, Number>();
		vv.setGraphMouse(graphMouse);

		JButton plus = new JButton("+");
		plus.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				scaler.scale(vv, 1.1f, vv.getCenter());
			}
		});

		JButton minus = new JButton("-");
		minus.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				scaler.scale(vv, 1 / 1.1f, vv.getCenter());
			}
		});

		ButtonGroup radio = new ButtonGroup();
		JRadioButton lineButton = new JRadioButton("Line");
		lineButton.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent e) {
				if (e.getStateChange() == ItemEvent.SELECTED) {
					vv.getRenderContext().setEdgeShapeTransformer(
							new EdgeShape.Line<Integer, Number>());
					vv.repaint();
				}
			}
		});

		JRadioButton quadButton = new JRadioButton("QuadCurve");
		quadButton.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent e) {
				if (e.getStateChange() == ItemEvent.SELECTED) {
					vv.getRenderContext().setEdgeShapeTransformer(
							new EdgeShape.QuadCurve<Integer, Number>());
					vv.repaint();
				}
			}
		});

		JRadioButton cubicButton = new JRadioButton("CubicCurve");
		cubicButton.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent e) {
				if (e.getStateChange() == ItemEvent.SELECTED) {
					vv.getRenderContext().setEdgeShapeTransformer(
							new EdgeShape.CubicCurve<Integer, Number>());
					vv.repaint();
				}
			}
		});
		radio.add(lineButton);
		radio.add(quadButton);
		radio.add(cubicButton);
		graphMouse.setMode(ModalGraphMouse.Mode.TRANSFORMING);

		JCheckBox rotate = new JCheckBox(
				"<html><center>EdgeType<p>Parallel</center></html>");
		rotate.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent e) {
				AbstractButton b = (AbstractButton) e.getSource();
				edgeLabelRenderer.setRotateEdgeLabels(b.isSelected());
				vv.repaint();
			}
		});
		rotate.setSelected(true);
		MutableDirectionalEdgeValue mv = new MutableDirectionalEdgeValue(.5, .7);
		vv.getRenderContext().setEdgeLabelClosenessTransformer(mv);
		JSlider directedSlider = new JSlider(mv.getDirectedModel()) {
			public Dimension getPreferredSize() {
				Dimension d = super.getPreferredSize();
				d.width /= 2;
				return d;
			}
		};
		JSlider undirectedSlider = new JSlider(mv.getUndirectedModel()) {
			public Dimension getPreferredSize() {
				Dimension d = super.getPreferredSize();
				d.width /= 2;
				return d;
			}
		};

		JSlider edgeOffsetSlider = new JSlider(0, 50) {
			public Dimension getPreferredSize() {
				Dimension d = super.getPreferredSize();
				d.width /= 2;
				return d;
			}
		};
		edgeOffsetSlider.addChangeListener(new ChangeListener() {

			public void stateChanged(ChangeEvent e) {
				JSlider s = (JSlider) e.getSource();
				AbstractEdgeShapeTransformer<Integer, Number> aesf = (AbstractEdgeShapeTransformer<Integer, Number>) vv
						.getRenderContext().getEdgeShapeTransformer();
				aesf.setControlOffsetIncrement(s.getValue());
				vv.repaint();
			}

		});

		Box controls = Box.createHorizontalBox();

		JPanel zoomPanel = new JPanel(new GridLayout(0, 1));
		zoomPanel.setBorder(BorderFactory.createTitledBorder("Scale"));
		zoomPanel.add(plus);
		zoomPanel.add(minus);

		JPanel edgePanel = new JPanel(new GridLayout(0, 1));
		edgePanel.setBorder(BorderFactory.createTitledBorder("EdgeType Type"));
		edgePanel.add(lineButton);
		edgePanel.add(quadButton);
		edgePanel.add(cubicButton);

		JPanel rotatePanel = new JPanel();
		rotatePanel.setBorder(BorderFactory.createTitledBorder("Alignment"));
		rotatePanel.add(rotate);

		JPanel labelPanel = new JPanel(new BorderLayout());
		JPanel sliderPanel = new JPanel(new GridLayout(3, 1));
		JPanel sliderLabelPanel = new JPanel(new GridLayout(3, 1));
		JPanel offsetPanel = new JPanel(new BorderLayout());
		offsetPanel.setBorder(BorderFactory.createTitledBorder("Offset"));
		sliderPanel.add(directedSlider);
		sliderPanel.add(undirectedSlider);
		sliderPanel.add(edgeOffsetSlider);
		sliderLabelPanel.add(new JLabel("Directed", JLabel.RIGHT));
		sliderLabelPanel.add(new JLabel("Undirected", JLabel.RIGHT));
		sliderLabelPanel.add(new JLabel("Edges", JLabel.RIGHT));
		offsetPanel.add(sliderLabelPanel, BorderLayout.WEST);
		offsetPanel.add(sliderPanel);
		labelPanel.add(offsetPanel);
		labelPanel.add(rotatePanel, BorderLayout.WEST);

		JPanel modePanel = new JPanel(new GridLayout(2, 1));
		modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode"));
		modePanel.add(graphMouse.getModeComboBox());

		controls.add(zoomPanel);
		controls.add(edgePanel);
		controls.add(labelPanel);
		controls.add(modePanel);
		content.add(controls, BorderLayout.SOUTH);
		quadButton.setSelected(true);
	}

	/**
	 * Generates a graph: in this case, reads it from the file
	 * "samples/datasetsgraph/simple.net"
	 * 
	 * @return A sample undirected graph
	 */

	public static SparseMultigraph<Integer, Number> getGraph()
			throws IOException {

		Factory<Integer> vertexFactory = new Factory<Integer>() {
			int n = 0;

			public Integer create() {
				return n++;
			}
		};
		Factory<Number> edgeFactory = new Factory<Number>() {
			int n = 0;

			public Integer create() {
				return n++;
			}
		};

		PajekNetReader<SparseMultigraph<Integer, Number>, Integer, Number> pnr = new PajekNetReader<SparseMultigraph<Integer, Number>, Integer, Number>(
				vertexFactory, edgeFactory);

		SparseMultigraph<Integer, Number> g = new SparseMultigraph<Integer, Number>();

		pnr.load("D:/jung/small.net", g);

		System.out.println("Num edges: " + g.getEdgeCount());
		System.out.println("Num Nodes: " + g.getVertexCount());

		return g;
	}

	// TODO given an edge find all routes that intersects with this edge
	// TODO can get help from database
	public List<Route> getIntersectedRoutes(Routes routes, NetEdge edge) {
		return null;
	}

	public List<Route> getIntersectedRoutes(String routeTable, NetEdge edge) {
		List<Route> resRoutes = data.getIntersectedRoutes(routeTable, edge);
		return resRoutes;
	}

	// TODO return true if the route and facility are within delta network
	// distance
	public boolean attracts(Route route, Facility facility, double delta) {
		// 1. get the list of edges for the route by doing NN search on each
		// point of the route
		// 2. if Eloc of facility is in internal edge, then return true
		// 3. else if Eloc of facility is on first or last edge, <br>
		// if Eloc of facility and Eloc of first or last point of route near
		// each other
		// return true
		// else return false;

		List<RouteNode> routeNodes = route.getCoords();
		List<NetEdge> netEdges = new ArrayList<NetEdge>();
		for (RouteNode rnode : routeNodes) {
			NearestNeighborQuery nnQuery = new NearestNeighborQuery();
			double[] point = { rnode.x, rnode.y };
			nnQuery.initWithPoint(tree, point);
			NetEdge nEdge = (NetEdge)nnQuery.getNearestObject();
			netEdges.add(nEdge);
		}
		
		//TODO smoothen the netEdges : if 111211 then 2->1 in mapmatching.
		 
		if (netEdges.get(0).getId() == facility.getEdgeId()){
			
		}else if (netEdges.get(netEdges.size()-1).getId() == facility.getEdgeId()){
			
		}else {
			for (NetEdge e : netEdges){
				if (e.getId() == facility.getEdgeId()){
					return true;
				}
			}
		}
		
		return false;
	}

	// TODO Given a coordinate, get its expected location on network
	public ELocation getExpLocation(Coordinate point) {
		// 1. index edges with in-memory RTree as line segments <br>
		// 2. do NN search with the point -> netedge <br>
		// 3. return expected location on the netedge <br>

		return null;
	}

	// public static Graph genGraph() {
	// Graph g;
	// NetNode n1, n2, n3, n4, n5;
	// n1 = new NetNode(1);
	// n2 = new NetNode(2);
	// n3 = new NetNode(3);
	// n4 = new NetNode(4);
	// n5 = new NetNode(5); // note n1-n5 declared elsewhere.
	// List<NetNode> nodes = new ArrayList<NetNode>(5);
	// nodes.add(n1);
	// nodes.add(n2);
	// nodes.add(n3);
	// nodes.add(n4);
	// nodes.add(n5);
	// for (NetNode n : nodes) {
	// g.addVertex(n);
	// }
	//
	// NetEdge e1 = new NetEdge(0, 2.0);
	// NetEdge e2 = new NetEdge(1, 2.0);
	// NetEdge e3 = new NetEdge(2, 2.0);
	//
	// // Add some edges. From above we defined these to be of type String
	// // Note that the default is for undirected edges.
	// g.addEdge(e1, n1, n2); // Note that Java 1.5 auto-boxes primitives
	// g.addEdge(e2, n2, n3);
	//
	// System.out.println("The graph g = " + g.toString());
	//
	// return g;
	// }

	class MutableDirectionalEdgeValue extends
			ConstantDirectionalEdgeValueTransformer<Integer, Number> {
		BoundedRangeModel undirectedModel = new DefaultBoundedRangeModel(5, 0,
				0, 10);
		BoundedRangeModel directedModel = new DefaultBoundedRangeModel(7, 0, 0,
				10);

		public MutableDirectionalEdgeValue(double undirected, double directed) {
			super(undirected, directed);
			undirectedModel.setValue((int) (undirected * 10));
			directedModel.setValue((int) (directed * 10));

			undirectedModel.addChangeListener(new ChangeListener() {
				public void stateChanged(ChangeEvent e) {
					setUndirectedValue(new Double(
							undirectedModel.getValue() / 10f));
					vv.repaint();
				}
			});
			directedModel.addChangeListener(new ChangeListener() {
				public void stateChanged(ChangeEvent e) {
					setDirectedValue(new Double(directedModel.getValue() / 10f));
					vv.repaint();
				}
			});
		}

		/**
		 * @return Returns the directedModel.
		 */
		public BoundedRangeModel getDirectedModel() {
			return directedModel;
		}

		/**
		 * @return Returns the undirectedModel.
		 */
		public BoundedRangeModel getUndirectedModel() {
			return undirectedModel;
		}
	}

}